package com.lagou.edu.mvcframework.servlet;

import com.lagou.edu.mvcframework.annonatations.*;
import com.lagou.edu.mvcframework.pojo.Handler;
import org.apache.commons.lang3.StringUtils;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class LgDispatcherServlet extends HttpServlet {

    private Properties properties = new Properties();

    // 缓存扫描到的类的全限定类名
    List<String> classNames = new ArrayList<>();

    //ioc容器
    Map<String,Object> ioc = new HashMap<>();

    //存储url和method
//    private Map<String,Object> handlerMapping = new HashMap<>();
    private List<Handler> handlerMapping = new ArrayList<>();

    @Override
    public void init(ServletConfig config) throws ServletException {
        try {
            //1.加载配置文件springmvc.properties
            String contextConfigLocation = config.getInitParameter("contextConfigLocation");
            doLoadConfig(contextConfigLocation);
            //2.扫描包,注解
            doScan(properties.getProperty("scanPackage"));
            //3.初始化ioc容器--基于注解
            doInstance();
            //4.依赖注入
            doAutowired();
            //5.建立url和method之间的关系
            initHandlerMapping();

            System.out.println("mvc初始化完成...");
            // 等待请求进入，处理请求

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //加载配置文件
    private void doLoadConfig(String contextConfigLocation) throws IOException {
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        properties.load(resourceAsStream);
    }

    // scanPackage: com.lagou.demo package----> 磁盘上的⽂件夹（File）
    private void doScan(String scanPackage) {
        String scanPackagePath = Thread.currentThread().getContextClassLoader().getResource("").getPath() + scanPackage.replaceAll("\\.", "/");
        File pack = new File(scanPackagePath);
        File[] files = pack.listFiles();
        for (File file : files) {
            if(file.isDirectory()){ //是否是子package
                //递归
                doScan(scanPackage + "." + file.getName());
            }else if(file.getName().endsWith(".class")){
                String className = scanPackage + "." + file.getName().replaceAll(".class","");
                classNames.add(className);
            }
        }

    }

    // 基于classNames缓存的类的全限定类名，以及反射技术，完成对象创建和管理
    private void doInstance() {
        if(classNames.size() == 0){return;}
        try {
            for (int i = 0; i < classNames.size(); i++) {
                String className =  classNames.get(i);
                Class<?> aClass = Class.forName(className);
                if(aClass.isAnnotationPresent(LgController.class)){
                    // controller的id此处不做过多处理，不取value了，就拿类的⾸字⺟⼩写作为id，保存到ioc中
                    String simpleName = aClass.getSimpleName();
                    String id = toLowerFirst(simpleName);
                    ioc.put(id,aClass.newInstance());
                }else if(aClass.isAnnotationPresent(LgService.class)){
                    LgService annotation = aClass.getAnnotation(LgService.class);
                    String value = annotation.value();
                    //如果不为空,则取value的值
                    if(!"".equalsIgnoreCase(value)){
                        ioc.put(value,aClass.newInstance());
                    }else {
                        //没有指定的话
                        value = toLowerFirst(aClass.getSimpleName());
                        ioc.put(value,aClass.newInstance());
                    }
                    //接口的也存入一份
                    Class<?>[] interfaces = aClass.getInterfaces();
                    for (int j = 0; j < interfaces.length; j++) {
                        Class<?> anInterface = interfaces[j];
                        // 以接⼝的全限定类名作为id放⼊
                        ioc.put(anInterface.getName(),aClass.newInstance());
                    }
                }else {
                    continue;
                }

            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    private void doAutowired() throws IllegalAccessException {
        if(ioc.isEmpty()){return;}

        // 遍历ioc中所有对象，查看对象中的字段，是否有@LagouAutowired注解，如果有需要维护依赖注⼊关系
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Field[] declaredFields = entry.getValue().getClass().getDeclaredFields();
            for (int i = 0; i < declaredFields.length; i++) {
                Field field = declaredFields[i];
                if(!field.isAnnotationPresent(LgAutowired.class)){
                    continue;
                }
                //有注解,处理...
                LgAutowired annotation = field.getAnnotation(LgAutowired.class);
                String value = annotation.value(); // 需要注⼊的bean的id
                //没有配置具体的bean id，那就需要根据当前字段类型注⼊（接⼝注⼊） IDemoService
                if("".equalsIgnoreCase(value)){
                    value = field.getType().getName();
                }
                //开启赋值
                field.setAccessible(true);
                field.set(entry.getValue(),ioc.get(value));
            }
        }
    }

    /*
         构造⼀个HandlerMapping处理器映射器
         最关键的环节
         ⽬的：将url和method建⽴关联
     */
    private void initHandlerMapping() {
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            // 获取ioc中当前遍历的对象的class类型
            Class<?> aClass = entry.getValue().getClass();
            if(!aClass.isAnnotationPresent(LgController.class)){continue;}
            LgRequestMapping annotation = aClass.getAnnotation(LgRequestMapping.class);
            String baseUrl = "";
            baseUrl = annotation.value(); //demo

            Method[] methods = aClass.getMethods();
            for (int i = 0; i < methods.length; i++) {
                Method method = methods[i];
                if(!method.isAnnotationPresent(LgRequestMapping.class)){
                    continue;
                }
                LgRequestMapping annotation1 = method.getAnnotation(LgRequestMapping.class);
                String methodUrl = annotation1.value();
                String url = baseUrl + methodUrl;

                // 把method所有信息及url封装为一个Handler
                Handler handler = new Handler(entry.getValue(),method, Pattern.compile(url));

                // 计算方法的参数位置信息  // query(HttpServletRequest request, HttpServletResponse response,String name)
                Parameter[] parameters = method.getParameters();
                for (int j = 0; j < parameters.length; j++) {
                    Parameter parameter = parameters[j];
                    // 如果是request和response对象，那么参数名称写HttpServletRequest和HttpServletResponse
                    if(parameter.getType() == HttpServletRequest.class || parameter.getType() == HttpServletResponse.class){
                        handler.getParamIndexMapping().put(parameter.getType().getSimpleName(),j);
                    }else {
                        handler.getParamIndexMapping().put(parameter.getName(),j);
                    }
                }
                handlerMapping.add(handler);
            }
        }
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req,resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 根据uri获取到能够处理当前请求的hanlder（从handlermapping中（list））
        Handler handler = null;
        if(handlerMapping.isEmpty()){return;}
        String uri = req.getRequestURI();
        for (Handler handler1 : handlerMapping) {
            Matcher matcher = handler1.getPattern().matcher(uri);
            if(!matcher.matches()){
                continue;
            }
            handler = handler1;
        }
        if(handler ==null){
            resp.getWriter().write("404");
            return;
        }

        //权限校验
        Method method = handler.getMethod();
        if(method.isAnnotationPresent(LgSecurity.class)){
            LgSecurity security = method.getAnnotation(LgSecurity.class);
            String[] value = security.value();
            String name = req.getParameter("name");
            if(!Arrays.asList(value).contains(name)){
                req.setCharacterEncoding("UTF-8");
                resp.setContentType("text/html; charset=utf-8");
                resp.getWriter().write("该用户:  " + name + "  没有权限,不能访问");
                return;
            }
        }


        //参数绑定
        // 获取所有参数类型数组，这个数组的长度就是我们最后要传入的args数组的长度
        Class<?>[] parameterTypes = handler.getMethod().getParameterTypes();
        // 根据上述数组长度创建一个新的数组（参数数组，是要传入反射调用的）
        Object[] paraValues = new Object[parameterTypes.length];
        //按顺序赋值
        Map<String, String[]> parameterMap = req.getParameterMap();
        for (Map.Entry<String, String[]> param : parameterMap.entrySet()) {
            // name=1&name=2   name [1,2]
            String value = StringUtils.join(param.getValue(), ",");
            // 如果参数和方法中的参数匹配上了，填充数据
            if(!handler.getParamIndexMapping().containsKey(param.getKey())){continue;}
            // 方法形参确实有该参数，找到它的索引位置，对应的把参数值放入paraValues
            Integer index = handler.getParamIndexMapping().get(param.getKey());
            paraValues[index] = value; // 把前台传递过来的参数值填充到对应的位置去
        }

        Integer requestIndex = handler.getParamIndexMapping().get(HttpServletRequest.class.getSimpleName());// 0
        paraValues[requestIndex] = req;

        Integer responseIndex = handler.getParamIndexMapping().get(HttpServletResponse.class.getSimpleName());// 0
        paraValues[responseIndex] = resp;


        // 最终调用handler的method属性
        try {
            handler.getMethod().invoke(handler.getController(),paraValues);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

    }

    public String toLowerFirst(String str){
        char[] chars = str.toCharArray();
        if(chars[0] >= 'A' && chars[0] <= 'Z'){
            chars[0] += 32;
        }
        return String.valueOf(chars);
    }
}
